﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
using System.Text;
using Isuka.Reflection;
using Isuka.Spiritual;
using Isuka.Spiritual.Appendix;

namespace Isuka.Cola.Auxiliaries
{
    /// <summary>
    /// 動的Delegate型生成
    /// .NET Framework 4.5 なら CustomTypeProvider が使えるのだろうか。
    /// </summary>
    internal class DynamicDelegateParser
    {
        class Grammer
            : CharGrammer<DelegateTypeBuilder>
        {
            public Grammer()
            {
                var definition = NewRule();
                var attributes = NewRule();
                var attributePart = NewRule();
                var resultType = NewRule();
                var callType = NewRule();
                var functionName = NewRule();
                var arguments = NewRule();
                var argument = NewRule();
                var identifier = DefinedCharRules<DelegateTypeBuilder>.Identifier;
                var fqdn = Enum(identifier, Eq('.'));
                var punk = ZeroWidth(x => char.IsPunctuation(x) || char.IsWhiteSpace(x));

                var typeNames = new[]
                {
                    Case("char", typeof(char).Name)         [b => b.PushType(typeof(char))],
                    Case("byte", typeof(byte).Name)         [b => b.PushType(typeof(byte))],
                    Case("short", typeof(short).Name)       [b => b.PushType(typeof(short))],
                    Case("int", typeof(int).Name)           [b => b.PushType(typeof(int))],
                    Case("long", typeof(long).Name)         [b => b.PushType(typeof(long))],
                    Case("ushort", typeof(ushort).Name)     [b => b.PushType(typeof(ushort))],
                    Case("uint", typeof(uint).Name)         [b => b.PushType(typeof(uint))],
                    Case("ulong", typeof(ulong).Name)       [b => b.PushType(typeof(ulong))],
                    Case("float", typeof(float).Name)       [b => b.PushType(typeof(float))],
                    Case("double", typeof(double).Name)     [b => b.PushType(typeof(double))],
                    Case("string", typeof(string).Name)     [b => b.PushType(typeof(string))],
                    Case("pointer", typeof(IntPtr).Name)    [b => b.PushType(typeof(IntPtr))],
                    Case("upointer", typeof(UIntPtr).Name)  [b => b.PushType(typeof(UIntPtr))],
                };
                var voidType
                    = Case("void", typeof(void).Name)[b => b.PushType(typeof(void))]
                    ;

                definition.Rule
                    = resultType % punk
                    % (callType % punk).ZeroOrOne()
                    % functionName
                    % "(" % arguments % ")"
                    ;

                resultType.Rule
                    = (Case(typeNames) | voidType)[b => b.SetResultType()]
                    ;

                callType.Rule
                    = Eq("stdcall")[b => SetCallingConvention(b, CallingConvention.StdCall)]
                    | Eq("cdecl")[b => SetCallingConvention(b, CallingConvention.Cdecl)]
                    | Eq("winapi")[b => SetCallingConvention(b, CallingConvention.Winapi)]
                    ;

                functionName.Rule
                    = identifier[(b, n) => b.DelegateInfo.Name = n]
                    ;

                arguments.Rule
                    = Enum(argument, Eq(',')).ZeroOrOne();
                ;

                argument.Rule
                    =
                    Epsilon[b => b.NewArgument()]
                    % (
                        attributes.ZeroOrOne()
                      % Case(typeNames)
                      % punk
                      % (identifier[(b, n) => b.CurrentArg.Name = n]).ZeroOrOne()
                    )[b => b.ComitArgument()]
                    ;

                attributes.Rule = '[' % Enum(attributePart, Eq(',')).ZeroOrOne() % ']';

                attributePart.Rule
                    =
                    (
                        Case("i", "in")[AddInAttr]
                        | Case("o", "out")[AddOutAttr]
                        | Case("r", "ref")[AddRefAttr]
                        | Eq("a")[b => AddUTAttr(b, "LPStr")]
                        | Eq("w")[b => AddUTAttr(b, "LPWStr")]
                        | Eq("t")[b => AddUTAttr(b, "LPTStr")]
                        | Case(System.Enum.GetNames(typeof(UnmanagedType)))[AddUTAttr]
                   )
                   ;

                this.Rule = definition;
                this.Skipper = SkipWhitespace;
            }

        }

        public static Type CreateDelegate(string signature)
        {
            Contract.Requires<ArgumentNullException>(signature != null, "signature");
            Contract.Ensures(Contract.Result<Type>() != null);

            var builder = new DelegateTypeBuilder();
            var grammer = new Grammer();

            var result = grammer.Run(signature, builder);
            if (!result.Match)
            {
                throw new ArgumentException();
            }
            return builder.CreateDelegateType();
        }

        static void SetCallingConvention(Auxiliaries.DelegateTypeBuilder b, CallingConvention c)
        {
            Contract.Requires(b != null);

            var cab = TypeHelper.CreateAttributeBuilder<UnmanagedFunctionPointerAttribute>(new { _ = c });
            b.DelegateInfo.Attributes.Add(cab);
        }

        static void AddInAttr(Auxiliaries.DelegateTypeBuilder b)
        {
            Contract.Requires(b != null);

            b.CurrentArg.Attributes.Add(TypeHelper.CreateAttributeBuilder<InAttribute>());
            b.CurrentArg.ParamAttributes |= ParameterAttributes.In;
        }

        static void AddOutAttr(Auxiliaries.DelegateTypeBuilder b)
        {
            Contract.Requires(b != null);

            b.CurrentArg.Attributes.Add(TypeHelper.CreateAttributeBuilder<OutAttribute>());
            b.CurrentArg.ParamAttributes |= ParameterAttributes.Out;
        }

        static void AddRefAttr(Auxiliaries.DelegateTypeBuilder b)
        {
            Contract.Requires(b != null);

            b.CurrentArg.ByRef = true;
        }

        static void AddUTAttr(Auxiliaries.DelegateTypeBuilder b, string name)
        {
            Contract.Requires(b != null);
            Contract.Requires(name != null);

            var val = (UnmanagedType)System.Enum.Parse(typeof(UnmanagedType), name.Trim());
            b.CurrentArg.Attributes.Add(TypeHelper.CreateAttributeBuilder<MarshalAsAttribute>(new { _ = val }));
        }
    }
}
